﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Management.Automation;
using System.Management.Automation.Provider;

using CrystalDecisions.Enterprise.Desktop;

namespace BO.Management.PowerShell.Provider
{
    [CmdletProvider("BoNavigation", ProviderCapabilities.None)]
    public class BusinessObjectNavigationProvider : NavigationCmdletProvider
    {

        /// <summary>
        /// The type of data pointed to by the path.
        /// </summary>
        private enum PathType
        {
            /// <summary>
            /// The path points to a business Object System = Root drive.
            /// </summary>
            Cms,


            /// <summary>
            /// The path points to a business Object Type.
            /// </summary>
            BoType,

            /// <summary>
            /// The path points to a Job Server.
            /// </summary>
            JobServer,

            /// <summary>
            /// The path is invalid
            /// </summary>
            Invalid
        }

        #region "Drive management"
        protected override System.Management.Automation.PSDriveInfo NewDrive(PSDriveInfo drive)
        {
            if (drive is BusinessObjectDriveInfo)
            {
                return drive;
            }

            var driveParams = this.DynamicParameters as BusinessObjectDriveParameters;
            return new BusinessObjectDriveInfo(drive, driveParams);
        }

        protected override object NewDriveDynamicParameters()
        {
            return new BusinessObjectDriveParameters();
        }

        protected override PSDriveInfo RemoveDrive(PSDriveInfo drive)
        {
            // Check if drive object is null.
            if (drive == null)
            {
                WriteError(new ErrorRecord(
                           new ArgumentNullException("drive"),
                           "NullDrive",
                           ErrorCategory.InvalidArgument,
                           drive));

                return null;
            }

            // Close the opened session.
            BusinessObjectDriveInfo driveInfo = drive as BusinessObjectDriveInfo;

            if (driveInfo == null)
            {
                WriteVerbose("drive Info is null");
                return null;
            }

            if (driveInfo.LogonProvider != null)
            {
                if (driveInfo.LogonProvider.Session == null)
                {
                    WriteVerbose("BOE Session is null");
                }
                else
                {

                    WriteVerbose("Logoff BOE session");
                    if (driveInfo.LogonProvider.Session.IsServerLogonSession()) driveInfo.LogonProvider.Session.Logoff();
                    driveInfo.LogonProvider.Dispose();
                }
            }

            return driveInfo;
        }
        #endregion

        /// <summary>
        /// Tests to see if the specified path is syntactically valid.
        /// </summary>
        /// <param name="path">The path to validate.</param>
        /// <returns>True if the specified path is valid.</returns>
        protected override bool IsValidPath(string path)
        {
            bool result = true;

            // Check to see if the path is null or empty.
            if (String.IsNullOrEmpty(path))
            {
                result = false;
            }

            // Convert all separators in the path to a uniform one.
            path = BO.Management.PowerShell.InternalHelper.ProviderNavigationHelper.NormalizePath(path);

            // Split the path into individual chunks.
            string[] pathChunks = path.Split(BO.Management.PowerShell.InternalHelper.ProviderNavigationHelper.PathSeparator.ToCharArray());

            foreach (string pathChunk in pathChunks)
            {
                if (pathChunk.Length == 0)
                {
                    result = false;
                }
            }

            return result;
        } // End IsValidPath method.

        /// <summary>
        /// Tests to see if the specified item exists.
        /// </summary>
        /// <param name="path">The path to the item to verify.</param>
        /// <returns>True if the item is found.</returns>
        protected override bool ItemExists(string path)
        {
            // Check if the path represented is a drive
            if (BO.Management.PowerShell.InternalHelper.ProviderNavigationHelper.PathIsDrive(this.PSDriveInfo.Root, path))
            {
                return true;
            }
            else
            {

                return true;
            }
        }

        /// <summary>
        /// The Windows PowerShell engine calls this method when the 
        /// Get-Item cmdlet is run and the path to this provider is 
        /// specified. This method retrieves an item using the specified path.
        /// </summary>
        /// <param name="path">The path to the item to be return.</param>
        protected override void GetItem(string path)
        {
            WriteVerbose("Start GetItem : " + path);
            // Check to see if the path represents a valid drive.
            if (BO.Management.PowerShell.InternalHelper.ProviderNavigationHelper.PathIsDrive(this.PSDriveInfo.Root, path))
            {
                WriteVerbose("Path is a drive; Return driveinfo");
                WriteItemObject(this.PSDriveInfo, path, true);
                return;
            } // End if(PathIsDrive...).

            // Get the BOE object type and the BOE object 
            // the necessary actions.
            string askedObject;

            ObjectKind type = this.GetNamesFromPath(path, out askedObject);
            WriteVerbose(String.Format("Found path type {0}", type));

            BusinessObjectDriveInfo driveInfo = this.PSDriveInfo as BusinessObjectDriveInfo;

            switch (type)
            {
                case ObjectKind.Server:
                    if (String.IsNullOrEmpty(askedObject))
                    {
                        WriteItemObject(type, path, true);
                    }
                    else
                    {
                        using (ServerProvider provider = new ServerProvider(driveInfo.LogonProvider))
                        {
                            ///List<CrystalDecisions.Enterprise.Desktop.Server> servers = provider.GetObject(askedObject);
                            //foreach (Server server in servers)
                            //{
                            //    WriteItemObject(server, path, false);
                            //}
                        }
                    }

                    break;
                case ObjectKind.Undefined:
                    ThrowTerminatingInvalidPathException(path);
                    break;
                default:
                    WriteVerbose(String.Format("This object type {0} is not yet implemented", type));
                    WriteError(new ErrorRecord(
                                                new Exception(
                                                                String.Format("This object type {0} is not yet implemented", type)),
                                                "1000", ErrorCategory.InvalidOperation, path
                                                )
                                );
                    break;
            }

        } // End GetItem method.

        /// <summary>
        /// Chunks the path and returns the type of "Business Objects" objects asked 
        /// from the path.
        /// Then It returns the last position of the asked object
        /// </summary>
        /// <param name="path">Path to chunk and obtain information</param>
        /// <param name="tableName">Name of the table as represented in the 
        /// path.</param>
        /// <returns>The type of BOE Object pointed to by the path.</returns>
        private ObjectKind GetNamesFromPath(string path, out string askedObject)
        {
            askedObject = null;

            // Check to see if the path specified is a drive.
            if (BO.Management.PowerShell.InternalHelper.ProviderNavigationHelper.PathIsDrive(this.PSDriveInfo.Root, path))
            {
                return ObjectKind.Cms;
            }

            // Chunks the path into parts.
            string[] pathChunks = BO.Management.PowerShell.InternalHelper.ProviderNavigationHelper.ChunkPath(this.PSDriveInfo.Root, path);

            //Check the object type of business Object
            BO.Management.ObjectKind type = ObjectKind.Undefined;

            try
            {
                type = (BO.Management.ObjectKind)Enum.Parse(typeof(BO.Management.ObjectKind), pathChunks[1],true);
            }
            catch (Exception ex)
            {
                WriteVerbose(String.Format("Unable to parse the path {0} as a BOE object type with exception {1}", pathChunks[1], ex));
                return ObjectKind.Undefined;
            }


            if (pathChunks.Length > 2)
            {

                for (Int32 i = 2; i < pathChunks.Length; i++)
                {
                    askedObject += pathChunks[i];
                    if ((i + 1) < pathChunks.Length) askedObject += @"\";
                }

            } // End switch(pathChunks...) statement.

            return type;
        } // End GetNamesFromPath method.


        protected override void GetChildItems(string path, bool recurse)
        {
            WriteVerbose("Start GetChildItems : " + path);
            // Check to see if the path represents a valid drive.
            if (BO.Management.PowerShell.InternalHelper.ProviderNavigationHelper.PathIsDrive(this.PSDriveInfo.Root, path))
            {
                String[] boTypes = Enum.GetNames(typeof(ObjectKind));
                foreach (String boType in boTypes)
                {
                    ObjectKind internalType = (BO.Management.ObjectKind)Enum.Parse(typeof(BO.Management.ObjectKind), boType,true);

                    if (internalType == ObjectKind.Undefined || internalType == ObjectKind.Cms) continue;

                    WriteItemObject(boType, path, true);

                    if (this.ItemExists(path) && recurse)
                    {
                        this.GetChildItems(path + BO.Management.PowerShell.InternalHelper.ProviderNavigationHelper.PathSeparator + boType, recurse);
                    }
                }

            } // End if(PathIsDrive...).

            // Get the BOE object type and the BOE object 
            // the necessary actions.
            string askedObject;

            ObjectKind type = this.GetNamesFromPath(path, out askedObject);
            WriteVerbose(String.Format("Found path type {0}", type));

            BusinessObjectDriveInfo driveInfo = this.PSDriveInfo as BusinessObjectDriveInfo;

            switch (type)
            {
                case ObjectKind.Server:
                    using (ServerProvider provider = new ServerProvider(driveInfo.LogonProvider))
                    {
                        List<CrystalDecisions.Enterprise.Desktop.Server> servers = provider.GetObject();
                        foreach (CrystalDecisions.Enterprise.Desktop.Server server in servers)
                        {
                            WriteItemObject(server, path + BO.Management.PowerShell.InternalHelper.ProviderNavigationHelper.PathSeparator + server.Title, false);
                        }
                    }
                    break;
                case ObjectKind.Undefined:
                    ThrowTerminatingInvalidPathException(path);
                    break;
                default:
                    WriteVerbose(String.Format("This object type {0} is not yet implemented", type));
                    WriteError(new ErrorRecord(
                                                new Exception(
                                                                String.Format("This object type {0} is not yet implemented", type)),
                                                "1000", ErrorCategory.InvalidOperation, path
                                                )
                                );
                    break;
            }
        }

        protected override void GetChildNames(string path, ReturnContainers returnContainers)
        {
            WriteVerbose("Start GetChildNames : " + path);
            // Check to see if the path represents a valid drive.
            if (BO.Management.PowerShell.InternalHelper.ProviderNavigationHelper.PathIsDrive(this.PSDriveInfo.Root, path))
            {
                String[] boTypes = Enum.GetNames(typeof(ObjectKind));
                foreach (String boType in boTypes)
                {
                    ObjectKind internalType = (BO.Management.ObjectKind)Enum.Parse(typeof(BO.Management.ObjectKind), boType, true);

                    if (internalType == ObjectKind.Undefined || internalType == ObjectKind.Cms) continue;

                    WriteItemObject(boType, path, true);

                }

                return;
            } // End if(PathIsDrive...).

            // Get the BOE object type and the BOE object 
            // the necessary actions.
            string askedObject;

            ObjectKind type = this.GetNamesFromPath(path, out askedObject);
            WriteVerbose(String.Format("Found path type {0}", type));

            BusinessObjectDriveInfo driveInfo = this.PSDriveInfo as BusinessObjectDriveInfo;

            switch (type)
            {
                case ObjectKind.Server:
                    using (ServerProvider provider = new ServerProvider(driveInfo.LogonProvider))
                    {
                        List<CrystalDecisions.Enterprise.Desktop.Server> servers = provider.GetObject();
                        foreach (CrystalDecisions.Enterprise.Desktop.Server server in servers)
                        {
                            WriteItemObject(server.Title, path, false);
                        }
                    }
                    break;
                case ObjectKind.Undefined:
                    ThrowTerminatingInvalidPathException(path);
                    break;
                default:
                    WriteVerbose(String.Format("This object type {0} is not yet implemented", type));
                    WriteError(new ErrorRecord(
                                                new Exception(
                                                                String.Format("This object type {0} is not yet implemented", type)),
                                                "1000", ErrorCategory.InvalidOperation, path
                                                )
                                );
                    break;
            }
 
        }

        protected override bool HasChildItems(string path)
        {
            WriteVerbose("Start GetChildNames : " + path);
            // Check to see if the path represents a valid drive.
            if (BO.Management.PowerShell.InternalHelper.ProviderNavigationHelper.PathIsDrive(this.PSDriveInfo.Root, path))
            {
                WriteVerbose("Path is a drive; Return driveinfo");
                return true;
            } // End if(PathIsDrive...).

            // Get the BOE object type and the BOE object 
            // the necessary actions.
            string askedObject;

            ObjectKind type = this.GetNamesFromPath(path, out askedObject);
            WriteVerbose(String.Format("Found path type {0}", type));

            BusinessObjectDriveInfo driveInfo = this.PSDriveInfo as BusinessObjectDriveInfo;

            switch (type)
            {
                case ObjectKind.Server:
                    return true;
                case ObjectKind.Undefined:
                    ThrowTerminatingInvalidPathException(path);
                    return false;
                default:
                    WriteVerbose(String.Format("This object type {0} is not yet implemented", type));
                    WriteError(new ErrorRecord(
                                                new Exception(
                                                                String.Format("This object type {0} is not yet implemented", type)),
                                                "1000", ErrorCategory.InvalidOperation, path
                                                )
                                );
                    return false;
            }

        }

        /// <summary>
        /// Throws an argument exception stating that the specified path does
        /// not represent either a table or a row.
        /// </summary>
        /// <param name="path">path which is invalid</param>
        private void ThrowTerminatingInvalidPathException(string path)
        {
            StringBuilder message = new StringBuilder("Path must represent either a BO type then a BO Object Name :");
            message.Append(path);

            throw new ArgumentException(message.ToString());
        }

    }
}
